﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Linq;
using System.Globalization;
using System.Windows.Media.Animation;
using System.Windows.Input;
using System.Collections.ObjectModel;

namespace Gmantis.Controls
{
    [TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates"), 
    TemplatePart(Name = "Text", Type = typeof(TextBoxBase)), 
    TemplateVisualState(Name = "Valid", GroupName = "ValidationStatesStates"), 
    TemplateVisualState(Name = "InvalidUnfocused", GroupName = "ValidationStatesStates"), 
    TemplateVisualState(Name = "InvalidFocused", GroupName = "ValidationStatesStates"), 
    StyleTypedProperty(Property = "ValidationDecoratorStyle", StyleTargetType = typeof(ValidationDecorator)), 
    TemplatePart(Name = "Browse", Type = typeof(Button)), TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates"), 
    TemplateVisualState(Name = "Disabled", GroupName = "CommonStates"), TemplateVisualState(Name = "Normal", GroupName = "CommonStates"), 
    TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
    public class FilePicker : Control, INotifyPropertyChanged
    {
        // Fields
        internal Button _elementBrowse;
        internal TextBoxBase _elementText;
        private OpenFileDialog _fileDialog;
        private bool _hasSelectedFiles;
        private bool _throwIsMouseOverChanged;
        public static readonly DependencyProperty BrowseContentProperty = DependencyProperty.Register("BrowseContent", typeof(object), typeof(FilePicker), new PropertyMetadata("\x00b7\x00b7\x00b7"));
        internal const string BrowseElementName = "Browse";
        public static readonly DependencyProperty ButtonBackgroundProperty = DependencyProperty.Register("ButtonBackground", typeof(Brush), typeof(FilePicker), null);
        public static readonly DependencyProperty ButtonForegroundProperty = DependencyProperty.Register("ButtonForeground", typeof(Brush), typeof(FilePicker), null);
        public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(FilePicker), null);
        public static readonly DependencyProperty DisabledCuesVisibilityProperty = DependencyProperty.Register("DisabledCuesVisibility", typeof(Visibility), typeof(FilePicker), new PropertyMetadata(Visibility.Visible));
        public static readonly DependencyProperty FilterIndexProperty = DependencyProperty.Register("FilterIndex", typeof(int), typeof(FilePicker), new PropertyMetadata(new PropertyChangedCallback(FilePicker.OnFilterIndexPropertyChanged)));
        public static readonly DependencyProperty FilterProperty = DependencyProperty.Register("Filter", typeof(string), typeof(FilePicker), new PropertyMetadata(new PropertyChangedCallback(FilePicker.OnFilterPropertyChanged)));
        public static readonly DependencyProperty FocusBrushProperty = DependencyProperty.Register("FocusBrush", typeof(Brush), typeof(FilePicker), null);
        public static readonly DependencyProperty FocusCuesVisibilityProperty = DependencyProperty.Register("FocusCuesVisibility", typeof(Visibility), typeof(FilePicker), new PropertyMetadata(Visibility.Visible));
        internal static readonly DependencyProperty ForceMouseOverProperty = DependencyProperty.Register("ForceMouseOver", typeof(bool), typeof(FilePicker), new PropertyMetadata(new PropertyChangedCallback(FilePicker.OnForceMouseOverPropertyChanged)));
        public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.Register("IsFocused", typeof(bool), typeof(FilePicker), new PropertyMetadata(new PropertyChangedCallback(FilePicker.OnIsFocusedPropertyChanged)));
        public static readonly DependencyProperty IsMouseOverProperty = DependencyProperty.Register("IsMouseOver", typeof(bool), typeof(FilePicker), new PropertyMetadata(new PropertyChangedCallback(FilePicker.OnIsMouseOverPropertyChanged)));
        public static readonly DependencyProperty MouseOverBrushProperty = DependencyProperty.Register("MouseOverBrush", typeof(Brush), typeof(FilePicker), null);
        public static readonly DependencyProperty MultiselectProperty = DependencyProperty.Register("Multiselect", typeof(bool), typeof(FilePicker), new PropertyMetadata(new PropertyChangedCallback(FilePicker.OnMultiselectPropertyChanged)));
        public static readonly DependencyProperty PressedBrushProperty = DependencyProperty.Register("PressedBrush", typeof(Brush), typeof(FilePicker), null);
        public static readonly DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile", typeof(FileInfo), typeof(FilePicker), new PropertyMetadata(new PropertyChangedCallback(FilePicker.OnSelectedFilePropertyChanged)));
        public static readonly DependencyProperty SelectedFilesProperty = DependencyProperty.Register("SelectedFiles", typeof(IEnumerable<FileInfo>), typeof(FilePicker), new PropertyMetadata(new PropertyChangedCallback(FilePicker.OnSelectedFilesPropertyChanged)));
        public static readonly DependencyProperty SelectionBackgroundProperty = DependencyProperty.Register("SelectionBackground", typeof(Brush), typeof(FilePicker), null);
        public static readonly DependencyProperty SelectionForegroundProperty = DependencyProperty.Register("SelectionForeground", typeof(Brush), typeof(FilePicker), null);
        public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register("TextAlignment", typeof(TextAlignment), typeof(FilePicker), new PropertyMetadata(TextAlignment.Left));
        internal const string TextElementName = "Text";
        internal static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(FilePicker), null);
        public static readonly DependencyProperty ValidationDecoratorStyleProperty = DependencyProperty.Register("ValidationDecoratorStyle", typeof(Style), typeof(FilePicker), null);
        public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register("Watermark", typeof(object), typeof(FilePicker), null);

        // Events
        public event EventHandler<PropertyChangedEventArgs<bool>> IsMouseOverChanged;
        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler SelectedFilesChanged;

        // Methods
        public FilePicker()
        {
            RoutedEventHandler handler = null;
            DependencyPropertyChangedEventHandler handler2 = null;
            this._throwIsMouseOverChanged = true;
            base.DefaultStyleKey = typeof(FilePicker);
            if (handler == null)
            {
                handler = delegate(object param0, RoutedEventArgs param1)
                {
                    this.ChangeVisualStateCommon(false);
                    this.ChangeVisualStateFocus(false);
                };
            }
            base.Loaded += handler;
            if (handler2 == null)
            {
                handler2 = (s, a) => this.ChangeVisualStateCommon(true);
            }
            base.IsEnabledChanged += handler2;
            base.GotFocus += new RoutedEventHandler(this.Control_GotFocus);
            base.LostFocus += new RoutedEventHandler(this.Control_LostFocus);
            base.MouseEnter += new MouseEventHandler(this.Control_MouseEnter);
            base.MouseLeave += new MouseEventHandler(this.Control_MouseLeave);
            this.SetCustomDefaultValues();
        }

        private void Browse()
        {
            if (this._fileDialog.ShowDialog().Value)
            {
                if (this.Multiselect)
                {
                    this.SelectedFiles = new ReadOnlyCollection<FileInfo>(this._fileDialog.Files.ToList<FileInfo>());
                }
                else
                {
                    this.SelectedFile = this._fileDialog.File;
                }
                this._hasSelectedFiles = true;
                this.OnSelectedFilesChanged(new EventArgs());
            }
        }

        private void Control_GotFocus(object sender, RoutedEventArgs e)
        {
            this.IsFocused = true;
        }

        private void Control_LostFocus(object sender, RoutedEventArgs e)
        {
            this.IsFocused = false;
        }

        private void Control_MouseEnter(object sender, MouseEventArgs e)
        {
            if (InputEventArgs.GetPointerType(e) == PointerDeviceType.Mouse)
            {
                this.IsMouseOver = true;
            }
        }

        private void Control_MouseLeave(object sender, MouseEventArgs e)
        {
            if (InputEventArgs.GetPointerType(e) == PointerDeviceType.Mouse)
            {
                this.IsMouseOver = false;
            }
        }

        private void FileBox_GotFocus(object sender, RoutedEventArgs e)
        {
            this.IsFocused = true;
        }

        private void FileBox_LostFocus(object sender, RoutedEventArgs e)
        {
            this.IsFocused = false;
        }

        private void FileBox_MouseEnter(object sender, MouseEventArgs e)
        {
            this.IsMouseOver = true;
        }

        private void FileBox_MouseLeave(object sender, MouseEventArgs e)
        {
            this.IsMouseOver = false;
        }

        protected void ChangeVisualStateCommon(bool useTransitions)
        {
            if (!base.IsEnabled)
            {
                VisualStateHelper.GoToState(this, "Disabled", useTransitions);
            }
            if (base.IsEnabled)
            {
                VisualStateHelper.GoToState(this, "MouseOver", useTransitions);
            }
            if ((base.IsEnabled && !this.IsMouseOver) && !this.ForceMouseOver)
            {
                VisualStateHelper.GoToState(this, "Normal", useTransitions);
            }
        }

        protected void ChangeVisualStateFocus(bool useTransitions)
        {
            if (!this.IsFocused)
            {
                VisualStateHelper.GoToState(this, "Unfocused", useTransitions);
            }
            if (this.IsFocused)
            {
                VisualStateHelper.GoToState(this, "Focused", useTransitions);
            }
        }

        public void ClearSelection()
        {
            this._hasSelectedFiles = false;
            this.SelectedFile = null;
            this.SelectedFiles = null;
            this._elementText.Text = string.Empty;
            this.OnSelectedFilesChanged(new EventArgs());
        }

        private void ElementBrowse_Click(object sender, RoutedEventArgs e)
        {
            this.Browse();
        }

        private void ElementText_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == Key.Delete)
            {
                this.ClearSelection();
            }
        }

        private T GetTemplateChild<T>(string childName, bool required, ref string errors) where T : class
        {
            DependencyObject templateChild = base.GetTemplateChild(childName);
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "template part", childName, templateChild, required, ref errors);
            return (templateChild as T);
        }

        private static Storyboard GetTemplateChildResource(FrameworkElement root, string resourceName, bool required, ref string errors)
        {
            return GetTemplateChildResource<Storyboard>(root, resourceName, required, ref errors);
        }

        private static T GetTemplateChildResource<T>(FrameworkElement root, string resourceName, bool required, ref string errors) where T : class
        {
            object child = root.Resources[resourceName];
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "resource", resourceName, child, required, ref errors);
            return (child as T);
        }

        private string GetTextValue()
        {
            return this._elementText.Text;
        }

        private void InitializeBrowsePart()
        {
            this._elementBrowse.Click += new RoutedEventHandler(this.ElementBrowse_Click);
        }

        private void InitializeTextPart()
        {
            base.GotFocus += new RoutedEventHandler(this.FileBox_GotFocus);
            base.LostFocus += new RoutedEventHandler(this.FileBox_LostFocus);
            base.MouseEnter += new MouseEventHandler(this.FileBox_MouseEnter);
            base.MouseLeave += new MouseEventHandler(this.FileBox_MouseLeave);
            this._elementText.KeyDown += new KeyEventHandler(this.ElementText_KeyDown);
            this.UpdateText();
        }

        private void OnAfterApplyTemplate()
        {
            this._elementText.IsReadOnly = true;
        }

        public override void OnApplyTemplate()
        {
            string errors = string.Empty;
            base.OnApplyTemplate();
            this._elementText = this.GetTemplateChild<TextBoxBase>("Text", true, ref errors);
            if (this._elementText != null)
            {
                this.InitializeTextPart();
            }
            this._elementBrowse = this.GetTemplateChild<Button>("Browse", true, ref errors);
            if (this._elementBrowse != null)
            {
                this.InitializeBrowsePart();
            }
            if (!string.IsNullOrEmpty(errors))
            {
                if ((base.Template != null) && !PlatformIndependent.IsInDesignMode(this))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Template cannot be applied to FilePicker.\nDetails: {0}", new object[] { errors }));
                }
            }
            else
            {
                this.ChangeVisualStateCommon(false);
                this.ChangeVisualStateFocus(false);
                this.OnAfterApplyTemplate();
            }
        }

        private void OnFilterChanged(string oldValue)
        {
            this._fileDialog.Filter = this.Filter;
        }

        private void OnFilterIndexChanged(int oldValue)
        {
            this._fileDialog.FilterIndex = this.FilterIndex;
        }

        private static void OnFilterIndexPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FilePicker picker = d as FilePicker;
            int oldValue = (int)e.OldValue;
            picker.OnFilterIndexChanged(oldValue);
        }

        private static void OnFilterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FilePicker picker = d as FilePicker;
            string oldValue = (string)e.OldValue;
            picker.OnFilterChanged(oldValue);
        }

        private static void OnForceMouseOverPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as FilePicker).ChangeVisualStateCommon(true);
        }

        private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as FilePicker).ChangeVisualStateFocus(true);
        }

        private static void OnIsMouseOverPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FilePicker sender = d as FilePicker;
            if ((sender.IsMouseOverChanged != null) && sender._throwIsMouseOverChanged)
            {
                PropertyChangedEventArgs<bool> args = new PropertyChangedEventArgs<bool>
                {
                    OldValue = (bool)e.OldValue,
                    NewValue = (bool)e.NewValue
                };
                sender.IsMouseOverChanged(sender, args);
            }
            sender.ChangeVisualStateCommon(true);
        }

        private void OnMultiselectChanged(bool oldValue)
        {
            this._fileDialog.Multiselect = this.Multiselect;
            this.UpdateText();
        }

        private static void OnMultiselectPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FilePicker picker = d as FilePicker;
            bool oldValue = (bool)e.OldValue;
            picker.OnMultiselectChanged(oldValue);
        }

        private void OnSelectedFileChanged(FileInfo oldValue)
        {
            this.UpdateText();
            this.RaisePropertyChanged("HasSelectedFiles");
            this.RaisePropertyChanged("SelectedFileName");
        }

        private static void OnSelectedFilePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FilePicker picker = d as FilePicker;
            FileInfo oldValue = (FileInfo)e.OldValue;
            picker.OnSelectedFileChanged(oldValue);
        }

        private void OnSelectedFilesChanged(IEnumerable<FileInfo> oldValue)
        {
            this.UpdateText();
            this.RaisePropertyChanged("HasSelectedFiles");
        }

        protected virtual void OnSelectedFilesChanged(EventArgs e)
        {
            if (this.SelectedFilesChanged != null)
            {
                this.SelectedFilesChanged(this, e);
            }
        }

        private static void OnSelectedFilesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FilePicker picker = d as FilePicker;
            IEnumerable<FileInfo> oldValue = (IEnumerable<FileInfo>)e.OldValue;
            picker.OnSelectedFilesChanged(oldValue);
        }

        public Stream OpenRead()
        {
            if (this.SelectedFile != null)
            {
                return this.SelectedFile.OpenRead();
            }
            return null;
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private void SetCustomDefaultValues()
        {
            this._fileDialog = new OpenFileDialog();
        }

        private void UpdateText()
        {
            if (this._elementText != null)
            {
                if (this.Multiselect)
                {
                    this._elementText.Text = string.Join(" ", this.SelectedFileNames.ToArray());
                }
                else
                {
                    this._elementText.Text = this.SelectedFileName;
                }
            }
        }

        // Properties
        public object BrowseContent
        {
            get { return GetValue(BrowseContentProperty); }
            set { SetValue(BrowseContentProperty, value); }
        }

        public Brush ButtonBackground
        {
            get { return (Brush)GetValue(ButtonBackgroundProperty); }
            set { SetValue(ButtonBackgroundProperty, value); }
        }

        public Brush ButtonForeground
        {
            get { return (Brush)GetValue(ButtonForegroundProperty); }
            set { SetValue(ButtonForegroundProperty, value); }
        }

        public CornerRadius CornerRadius
        {
            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }

        public Visibility DisabledCuesVisibility
        {
            get { return (Visibility)GetValue(DisabledCuesVisibilityProperty); }
            set { SetValue(DisabledCuesVisibilityProperty, value); }
        }

        public string Filter
        {
            get { return (string)GetValue(FilterProperty); }
            set { SetValue(FilterProperty, value); }
        }

        public int FilterIndex
        {
            get { return (int)GetValue(FilterIndexProperty); }
            set { SetValue(FilterIndexProperty, value); }
        }

        public Brush FocusBrush
        {
            get { return (Brush)GetValue(FocusBrushProperty); }
            set { SetValue(FocusBrushProperty, value); }
        }

        public Visibility FocusCuesVisibility
        {
            get { return (Visibility)GetValue(FocusCuesVisibilityProperty); }
            set { SetValue(FocusCuesVisibilityProperty, value); }
        }

        internal bool ForceMouseOver
        {
            get { return (bool)GetValue(ForceMouseOverProperty); }
            set { SetValue(ForceMouseOverProperty, value); }
        }

        public bool HasSelectedFiles
        {
            get { return this._hasSelectedFiles; }
        }

        public bool IsFocused
        {
            get { return (bool)GetValue(IsFocusedProperty); }
            private set { SetValue(IsFocusedProperty, value); }
        }

        public bool IsMouseOver
        {
            get { return (bool)GetValue(IsMouseOverProperty); }
            internal set { SetValue(IsMouseOverProperty, value); }
        }

        public Brush MouseOverBrush
        {
            get { return (Brush)GetValue(MouseOverBrushProperty); }
            set { SetValue(MouseOverBrushProperty, value); }
        }

        public bool Multiselect
        {
            get { return (bool)GetValue(MultiselectProperty); }
            set { SetValue(MultiselectProperty, value); }
        }

        public Brush PressedBrush
        {
            get { return (Brush)GetValue(PressedBrushProperty); }
            set { SetValue(PressedBrushProperty, value); }
        }

        public FileInfo SelectedFile
        {
            get { return (FileInfo)GetValue(SelectedFileProperty); }
            set { SetValue(SelectedFileProperty, value); }
        }

        public string SelectedFileName
        {
            get
            {
                if (this.SelectedFile != null)
                    return this.SelectedFile.Name;

                return string.Empty;
            }
        }

        public List<string> SelectedFileNames
        {
            get
            {
                List<string> list = new List<string>();
                if (this.SelectedFiles != null)
                {
                    foreach (string str in from f in this.SelectedFiles select f.Name)
                    {
                        list.Add("\"" + str + "\"");
                    }
                }
                return list;
            }
        }

        public IEnumerable<FileInfo> SelectedFiles
        {
            get { return (IEnumerable<FileInfo>)GetValue(SelectedFilesProperty); }
            set { SetValue(SelectedFilesProperty, value); }
        }

        public Brush SelectionBackground
        {
            get { return (Brush)GetValue(SelectionBackgroundProperty); }
            set { SetValue(SelectionBackgroundProperty, value); }
        }

        public Brush SelectionForeground
        {
            get { return (Brush)GetValue(SelectionForegroundProperty); }
            set { SetValue(SelectionForegroundProperty, value); }
        }

        internal string Text
        {
            get
            {
                return this.GetTextValue();
            }
            set { SetValue(TextProperty, value); }
        }

        public TextAlignment TextAlignment
        {
            get { return (TextAlignment)GetValue(TextAlignmentProperty); }
            set { SetValue(TextAlignmentProperty, value); }
        }

        public Style ValidationDecoratorStyle
        {
            get { return (Style)GetValue(ValidationDecoratorStyleProperty); }
            set { SetValue(ValidationDecoratorStyleProperty, value); }
        }

        public object Watermark
        {
            get { return GetValue(WatermarkProperty); }
            set { SetValue(WatermarkProperty, value); }
        }
    }
}
